<!--// 组件：树
/*
参数说明-属性：
1.treeData：展示数据（array）
2.treeEmptyText:内容为空的时候展示的文本(String)
3.treeNodeKey：每个树节点用来作为唯一标识的属性，整棵树应该是唯一的(String)
4.treeRenderAfterExpand:是否在第一次展开某个树节点后才渲染其子节点(boolean)
5.treeHighlightCurrent:是否高亮当前选中节点，默认值是 false(boolean)
6.treeDefaultExpandAll:是否默认展开所有节点(boolean)
7.treeExpandOnClickNode:是否在点击节点的时候展开或者收缩节点， 默认值为 true，如果为 false，则只有点箭头图标的时候才会展开或者收缩节点(boolean)
8.treeCheckOnClickNode:是否在点击节点的时候选中节点，默认值为 false，即只有在点击复选框时才会选中节点。(boolean)
9.treeDefaultExpandedKeys:默认展开的节点的 key 的数组(array)
10.treeShowCheckbox:节点是否可被选择(boolean)
11.treeCheckStrictly:在显示复选框的情况下，是否严格的遵循父子不互相关联的做法，默认为 false(boolean)
12.treeDefaultCheckedKeys:默认勾选的节点的 key 的数组(array)
13.treeAccordion:是否每次只打开一个同级树节点展开(boolean)
14.treeIndent:相邻级节点间的水平缩进，单位为像素(number)
15.treeIconClass:自定义树节点的图标(string)
16.treeFilterText：对树节点进行筛选操作(string)

17.treeNodeSelect:节点点击回调函数的函数名
  返回参数：s：有复选框时，选中节点的信息,
            a：点击节点在data中对应的对象,
            b：节点对应的 Node
18.treeNodeCheckChange:节点选中状态发生变化时的回调函数的函数名
  返回参数：a：传递给 data 属性的数组中该节点所对应的对象,
            b：节点本身是否被选中,
            c：节点的子树中是否有被选中的节点
            说明：现在返回参数报错（TypeError: Cannot read property 'toLowerCase' of undefined）不知道啥原因，暂时不返回
19.treeNodeCheck:当复选框被点击的时候触发回调函数的函数名
  返回参数：a:传递给 data 属性的数组中该节点所对应的对象
            b:树目前的选中状态对象，包含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四个属性
20.treeNodeExpand:节点展开事件
21.treeNodeCollapse:节点关闭事件
22.showMenu:是否显示右键菜单
23.unselectNode 参数，为真时不显示选中节点背景色
参数说明-事件：
1.node-click:节点被点击时的回调(共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。)
2.check-change：节点选中状态发生变化时的回调（共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点）
3.check：当复选框被点击的时候触发（共两个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、树目前的选中状态对象，包含 checkedNodes、checkedKeys、halfCheckedNodes、halfCheckedKeys 四个属性）
4.node-drop:拖拽成功完成时触发的事件	共四个参数，依次为：被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置（before、after、inner）、event
*/-->
<template>
  <div
    :class="[
      'tree-div',
      { 'unselect-node': unselectNode === 0 && treeShowCheckbox },
    ]"
    :style="{ height: pageHeight }"
  >
    <el-input
      v-if="showSearch"
      ref="searchInput"
      v-model="filterText"
      class="search-input"
      size="mini"
      placeholder="输入关键字进行过滤"
      @keyup.enter.native="filterMthods"
    >
      <slot slot="append" name="menu" />
    </el-input>
    <el-tree
      ref="modelTree"
      v-loading="loading"
      :data="treeData"
      :style="{ height: treeElHeight }"
      :empty-text="treeEmptyText"
      node-key="id"
      :render-after-expand="treeRenderAfterExpand"
      :highlight-current="false"
      :default-expand-all="treeDefaultExpandAll"
      :expand-on-click-node="treeExpandOnClickNode"
      :check-on-click-node="treeCheckOnClickNode"
      :default-expanded-keys="treeDefaultExpandedKeys"
      :show-checkbox="treeShowCheckbox"
      :check-strictly="treeCheckStrictly"
      :default-checked-keys="treeDefaultCheckedKeys"
      :accordion="treeAccordion"
      :indent="treeIndent"
      :icon-class="treeIconClass"
      :filter-node-method="filterNode"
      :show-menu="showMenu"
      :draggable="draggable"
      :allow-drop="allowDrop"
      :allow-drag="allowDrag"
      :props="defaultProps"
      @node-drag-end="handleDragEnd"
      @node-expand="nodeExpand"
      @node-collapse="nodeCollapse"
      @node-click="clickNode"
      @node-drop="nodeDrop"
      @check-change="checkChange"
      @check="check"
      @click.prevent.native="unClick"
      @contextmenu.prevent.native="openMenu($event)"
      @current-change="changeCur"
    >
      <span slot-scope="{ node, data }" class="custom-tree-node">
        <span>
          <svg-icon :icon-class="data.icon || getIcon(data)" />
          <span
            class="el-tree-node__label"
            :style="{ color: data.color }"
            :title="node.label"
          >{{ node.label }}
          </span>
        </span>
        <span v-show="showRightTag">
          <el-tag
            v-if="data.tag !== undefined"
            :effect="data.tag.effect"
            :type="data.tag.type"
          >{{ data.tag.text }}</el-tag>
        </span>
      </span>
    </el-tree>
    <ul
      v-show="visible"
      ref="contextmenu"
      :style="{ left: left + 'px', top: top + 'px' }"
      class="contextmenu"
    >
      <slot :node="selectNode" />
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    treeData: {
      type: Array,
      default: () => {
        return []
      }
    },
    treeEmptyText: {
      type: String,
      default: '暂无数据'
    },
    // treeNodeKey: {
    //     type: String
    // },
    treeRenderAfterExpand: {
      type: Boolean,
      default: true
    },
    treeHighlightCurrent: {
      type: Boolean,
      default: false
    },
    treeDefaultExpandAll: {
      type: Boolean,
      default: false
    },
    treeExpandOnClickNode: {
      type: Boolean,
      default: true
    },
    treeCheckOnClickNode: {
      type: Boolean,
      default: false
    },
    treeDefaultExpandedKeys: {
      type: Array,
      default: () => {
        return []
      }
    },
    treeShowCheckbox: {
      type: Boolean,
      default: false
    },
    treeCheckStrictly: {
      type: Boolean,
      default: false
    },
    treeDefaultCheckedKeys: {
      type: Array,
      default: () => {
        return []
      }
    },
    treeAccordion: {
      type: Boolean,
      default: false
    },
    showMenu: {
      type: Boolean,
      default: false
    },
    treeIndent: {
      type: Number,
      default: 16
    },
    treeIconClass: {
      type: String,
      default: ''
    },
    treeFilterText: {
      type: String,
      default: ''
    },

    showSearch: {
      type: Boolean,
      default: true
    },
    showRightTag: {
      type: Boolean,
      default: false
    },
    treeHeight: {
      type: [String, Number],
      default: 0
    },
    draggable: {
      type: Boolean,
      default: false
    },
    compTreeHeight: {
      type: [String, Number],
      default: ''
    },
    // 是否禁用过滤
    filterDisabeld: {
      type: Boolean,
      default: false
    },
    // 禁用选中背景色
    unselectNode: {
      type: Boolean,
      default: false
    },
    treeNodeSelect: {
      type: Function,
      default: () => {}
    },
    // 开启点击标题取消当前选中（弹窗模式）
    isUnselect: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      filterText: '',
      defaultProps: {
        children: 'nodes',
        label: 'text'
      },
      visible: false,
      top: 0,
      left: 0,
      selectNode: null,
      treeElHeight: 0,
      pageHeight: '100%',
      loading: false,
      lastSelectedNodeInfo: { time: 0, node: null },
      folderCloseIcon: 'wenjianyi',
      folderOpenIcon: 'wenjianer'
    }
  },
  watch: {
    filterText(val) {
      if (this.filterDisabeld) return
      this.$refs.modelTree.filter(val)
    },
    treeFilterText(val) {
      if (this.filterDisabeld) return
      this.$refs.modelTree.filter(val)
    },
    visible(value) {
      if (value) {
        document.body.addEventListener('click', this.closeMenu)
        this.selectNode = this.$refs.modelTree.getCurrentNode()
      } else {
        document.body.removeEventListener('click', this.closeMenu)
        this.selectNode = null
      }
    },
    showSearch() {
      this.treeElHeight = this.getTreeHeight()
    }
  },
  mounted() {
    const that = this
    that.treeElHeight = this.getTreeHeight()
    // 如果有compTreeHeight传递
    if (this.compTreeHeight !== '') {
      const compTreeHeight = this.compTreeHeight
      if (typeof compTreeHeight === 'string') {
        if (compTreeHeight.includes('px')) {
          that.pageHeight = compTreeHeight
        } else {
          that.pageHeight = compTreeHeight + 'px'
        }
      } else {
        that.pageHeight = compTreeHeight + 'px'
      }
    }
    // 初始化点击标题取消当前选中
    if (this.isUnselect) this.initUnselect()
  },
  methods: {
    getIcon(data) {
      let icon = ''
      if (data.folder) {
        icon = this.folderCloseIcon
      }
      return icon
    },
    filterNode(value, data) {
      if (this.filterDisabeld) return
      if (!value) return true
      return data.text.indexOf(value) !== -1
    },
    // 搜索框键盘回车事件
    filterMthods() {
      this.$emit('filter-method', this.filterText)
    },
    // 选中、或双击
    clickNode(a, b, c) {
      const info = this.lastSelectedNodeInfo
      const t = new Date().getTime()
      if (t - info.time < 300 && info.node.id === a.id) {
        // 进行双击事件
        this.$emit('treeNodeDbClick', a, b, c)
      } else {
        if (a.disabled) {
          this.$refs.modelTree.setCurrentKey(null)
        } else {
          this.visible = false
          // 进行单击事件
          this.treeNodeSelect(a, b, c)
        }
      }
      info.time = t
      info.node = a
    },
    // 节点拖拽
    nodeDrop(before, after, inner) {
      this.$emit('treeNodeNodeDrop', before, after, inner)
    },
    checkChange(a, b, c) {
      this.$emit('treeNodeCheckChange', a, b, c)
    },
    check(a, b) {
      this.$emit('treeNodeCheck', a, b)
    },
    nodeExpand(a, b, c) {
      if (a.folder) {
        this.$set(a, 'icon', this.folderOpenIcon)
      }
      this.$emit('treeNodeExpand', a, b, c)
    },
    handleDragEnd(a, b, c, d) {
      this.$emit('treeNodeDragEnd', a, b, c, d)
    },
    nodeCollapse(a, b, c) {
      if (a.folder) {
        this.$set(a, 'icon', this.folderCloseIcon)
      }
      this.$emit('treeNodeCollapse', a, b, c)
    },
    // 取消选中
    unClick() {
      this.$refs.modelTree.setCurrentKey(null)
      // 触发事件让父组件知道取消选中
      this.$emit('unClick', null)
    },
    openMenu(e) {
      if (!this.showMenu) return
      // const menuMinWidth = 10
      const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
      const offsetWidth = this.$el.offsetWidth // container width
      // const maxLeft = offsetWidth - menuMinWidth // left boundary
      const left = e.clientX - offsetLeft + 15 // 15: margin right
      if (left > offsetWidth) {
        this.left = offsetWidth
      } else {
        this.left = left
      }
      // 获取html可视区域高度：
      const clientHeight = `${document.documentElement.clientHeight}`
      const contextmenuDomH = 262 // contextmenuDomH的高度
      // 如果contextmenuDom的位置大于了屏幕高度的话，就让contextmenuDom显示在底部
      if (e.clientY + contextmenuDomH > parseInt(clientHeight)) {
        this.top = parseInt(clientHeight) - 360
      } else {
        this.top = e.clientY - this.$el.getBoundingClientRect().top
      }
      this.visible = true
    },
    // 当前选中节点变化时触发的事件
    changeCur(a) {
      this.$emit('selectNode', a)
    },
    closeMenu(e) {
      var dom = e.target
      this.visible = this.hasClass(dom, 'disabled')
    },
    hasClass(element, cls) {
      return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1
    },
    showLoading() {
      this.loading = true
    },
    hideLoading() {
      setTimeout(() => {
        this.loading = false
      }, 500)
    },
    allowDrop(draggingNode, dropNode, type) {
      return dropNode.data.folder === 1
    },
    allowDrag(draggingNode) {
      return true
    },
    setChecked(node, checked, deep) {
      this.$nextTick(() => {
        this.$refs.modelTree.setChecked(node, checked, deep)
      })
    },
    setSelect(a) {
      this.$nextTick(() => {
        this.$refs.modelTree.setCurrentKey(a)
      })
    },
    getCheckedNodes(leafOnly, includeHalfChecked) {
      return this.$refs.modelTree.getCheckedNodes(leafOnly, includeHalfChecked)
    },
    // 获取当前选中的节点
    getCurrentNode() {
      return this.$refs.modelTree.getCurrentNode()
    },
    // 为 Tree 中的一个节点追加一个子节点
    addNode(node, pNode) {
      this.$refs.modelTree.append(node, pNode)
    },
    // 删除 Tree 中的一个节点
    removeNode(data) {
      this.$refs.modelTree.remove(data)
    },
    // 为 Tree 的一个节点的前面增加一个节点
    insertBefore(node, pNode) {
      this.$refs.modelTree.insertBefore(node, pNode)
    },
    // 为 Tree 的一个节点的后面增加一个节点
    insertAfter(node, pNode) {
      this.$refs.modelTree.insertAfter(node, pNode)
    },
    getTreeHeight() {
      const h1 = this.showSearch ? this.$refs.searchInput.$el.clientHeight : 0
      return `calc(100% - ${h1}px)`
    },
    getPageHeight() {
      let h
      const type = typeof this.treeHeight
      if (type === 'number') {
        h = globalConfig.IS_AOM_WEB ? 100 : 200 // （这里的200是减去表格上面导航和搜索条件的高度）
        h =
          (window.innerHeight ||
            document.documentElement.clientHeight ||
            document.body.clientHeight) -
          h +
          'px'
      } else {
        h = this.treeHeight
      }
      return h
    },
    setCheckedKeys(keyArr, flag) {
      return this.$refs.modelTree.setCheckedKeys(keyArr, flag)
    },
    getNode(id) {
      return this.$refs.modelTree.getNode(id)
    },
    // 高亮某一行
    setCurrentKey(key) {
      this.$nextTick(() => {
        this.$refs.modelTree.setCurrentKey(key)
      })
    },
    // 根据key展开某一行
    expandedByKey(key) {
      this.$nextTick(() => {
        this.$refs.modelTree.store.nodesMap[key].expanded = true
      })
    },
    // 如果是弹窗使用树 那么可以通过点击标题位置进行 取消当前选中的节点
    initUnselect() {
      this.$parent.$el.querySelector('.el-dialog__header').onclick = (e) => {
        this.$refs.modelTree.setCurrentKey(null)
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.search-input {
  padding-bottom: 5px;
  ::v-deep .el-input-group__append {
    background-color: #fff;
    padding-right: 5px;
    padding-left: 5px;
    .el-dropdown-selfdefine {
      padding-left: 5px;
      padding-right: 5px;
    }
    .el-button {
      margin-left: 0;
      margin-right: 0;
    }
  }
}

.el-tree::-webkit-scrollbar {
  /*滚动条整体样式*/
  width: 7px;
  /*高宽分别对应横竖滚动条的尺寸*/
  height: 1px;
}

.el-tree::-webkit-scrollbar-thumb {
  /*滚动条里面小方块*/
  border-radius: 10px;
  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  background: #ccc;
}

.el-tree::-webkit-scrollbar-track {
  /*滚动条里面轨道*/
  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
  border-radius: 10px;
  background: #ededed;
}

.el-tree {
  overflow-y: auto;
  border: 1px solid #ccc;
  border-radius: 3px;
  overflow-y: auto;
  overflow-x: hidden;
  //选中样式
  ::v-deep .el-tree-node.is-current > .el-tree-node__content {
    background-color: #c1e8f2;
  }

  ::v-deep .el-tree-node__content {
    height: 30px;
    border-bottom: 1px solid #ccc;
    span {
      font-size: 12px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }

  ::v-deep .el-tree-node__content :last-child() {
    border-bottom: none;
  }

  ::v-deep div[aria-disabled="true"] {
    .el-tree-node__content {
      cursor: not-allowed;
    }
    color: silver;
  }

  .svg-icon {
    transform: translateY(1px);
    margin-right: 2px;
  }
}

//定制xs样式
.tree-div.xs .el-tree {
  ::v-deep .el-tree-node__content {
    height: 24px;
  }
}

.tree-div {
  ::v-deep .el-input-group__append {
    padding-right: 0;
  }
}

//禁用选中背景色
.tree-div.unselect-node {
  ::v-deep .el-tree-node.is-current > .el-tree-node__content {
    background-color: transparent;
  }
}

//右键菜单样式
.contextmenu {
  width: 150px;
  margin: 0;
  background: #fff;
  z-index: 3000;
  position: absolute;
  list-style-type: none;
  padding: 5px 0;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 400;
  color: #333;
  // box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(0, 0, 0, 0.15);
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
  ::v-deep li {
    margin: 0;
    padding: 5px 20px;
    cursor: pointer;
    &:hover {
      background: #eee;
    }
  }
  ::v-deep .el-divider--horizontal {
    height: 1px;
    // margin: 9px 0;
    overflow: hidden;
    background-color: #e5e5e5;
    margin: 6px 0;
  }
}
.custom-tree-node {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 14px;
  padding-right: 8px;
}
</style>

